package com.cjx.stars.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.lang.Pair;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.cjx.stars.common.MyException;
import com.cjx.stars.common.Response;
import com.cjx.stars.common.ResponseUtils;
import com.cjx.stars.constant.FailMessage;
import com.cjx.stars.mapper.UserMapper;
import com.cjx.stars.pojo.table.User;
import com.cjx.stars.pojo.vo.UserVo;
import com.cjx.stars.pojo.request.Login;
import com.cjx.stars.pojo.request.Register;
import com.cjx.stars.service.FollowService;
import com.cjx.stars.service.UserService;
import com.cjx.stars.utils.*;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;

import java.lang.invoke.VarHandle;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.cjx.stars.constant.EmailConstant.UPDATE_EMAIL_CONTENT;
import static com.cjx.stars.constant.EmailConstant.UPDATE_EMAIL_TITLE;
import static com.cjx.stars.constant.FailMessage.*;
import static com.cjx.stars.constant.RedisConstant.*;
import static com.cjx.stars.constant.SystemConstants.*;
import static com.cjx.stars.constant.UserConstant.DEFAULT_AVATER_URL;
import static com.cjx.stars.constant.UserConstant.USER_KEY_PREFIX;
import static com.cjx.stars.utils.RegexValidationUtils.*;

/**
 * @author cjx
 * @description 针对表【t_user(用户表)】的数据库操作Service实现
 * @createDate 2024-02-02 14:44:06
 */
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService {

    @Resource
    private UserMapper userMapper;
    @Resource
    private RedisIdWorker redisIdWorker;
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    @Resource
    private RedisHelper redisHelper;
    @Resource
    private FollowService followService;
    @Resource
    private EmailUtilImpl emailUtil;

    @Override
    public Response<?> register(Register userRegister) {
//        1. 前端传递账号，用户名，密码，确认密码。用户名可以为空
        String account = userRegister.getAccount();
        String password = userRegister.getPassword();
        String checkPassword = userRegister.getCheckPassword();
        String phone = userRegister.getPhone();
        String captcha = userRegister.getCaptcha();
        //关键数据为空直接返回错误信息
        if (StrUtil.isAllBlank(account, password, checkPassword, phone, captcha)) {
            return ResponseUtils.fail(ERROR_PARAMETER_IS_BLACK);
        }
//        2. 校验三次格式
        if (!(checkAccount(account) && checkPassword(password) && checkPassword(checkPassword) && checkCaptcha(captcha) && validatePhoneNumber(phone))) {
            return ResponseUtils.fail(ERROR_INCORRECT_FORMAT);
        }
//        3. 校验用户名是否为敏感字符
//        4. 校验两次密码是否一致
        if (!password.equals(checkPassword)) {
            return ResponseUtils.fail(FailMessage.ERROR_PASSWORD_INCONSISTENT);
        }
        //判断验证码是否过期或一致
        String code = stringRedisTemplate.opsForValue().get(USER_REGISTER_CODE_KEY + phone);
        // 验证码已过期
        if (StrUtil.isBlank(code)) {
            return ResponseUtils.fail(FailMessage.ERROR_VERIFICATION_CODE_HAS_EXPIRED);
        }
        if (!captcha.equals(code)) {
            return ResponseUtils.fail(FailMessage.ERROR_VERIFICATION_CODE_IS_INCONSISTENT);
        }
//        5. 查询数据库判断账号是否重复
        Long count = userMapper.selectCount(new QueryWrapper<User>().eq("account", account));
        if (count > 0) {
            return ResponseUtils.fail(FailMessage.ERROR_USERNAME_REPETITION);
        }
        Long userid = createRegisterUser(userRegister, password);
        // 注册成功后执行登录成功逻辑，建立登陆态
        //创建token
        String token = IdUtil.simpleUUID();
        User user = getUserById(userid);
        redisHelper.setLoginKey(user, token);
//        8. 返回创建成功状态码给前端
        return ResponseUtils.success(token);
    }

    public Long createRegisterUser(Register userRegister, String password) {
        //        6. 生成用户id
        long id = redisIdWorker.createId(USER_KEY_PREFIX);
//        7. 将新用户信息存入数据库
        //将密码加密
        String encryptPassword = DigestUtil.md5Hex(password);
        User user = BeanUtil.copyProperties(userRegister, User.class);
        user.setUserId(id);
        user.setAvatarUrl(DEFAULT_AVATER_URL);
        user.setPassword(encryptPassword);
        //添加用户名
        user.setUsername(generateUsername());
        save(user);
        return id;
    }

    @Override
    public Response<String> login(Login userLogin) {
//        1. 前端传递账号，密码。
        String account = userLogin.getAccount();
        String password = userLogin.getPassword();
//        2. 判空
        if (StrUtil.isAllBlank(account, password)) {
            throw new MyException(ERROR_PARAMETER_IS_BLACK);
        }
//        3. 校验格式（优化）
        if (!(checkAccount(account) && checkPassword(password))) {
            throw new MyException(ERROR_INCORRECT_FORMAT);
        }
//        4. 根据账号和密码查询数据库
//                - 密码加密查询
        String encryptPassword = DigestUtil.md5Hex(password);
        User user = getOne(new QueryWrapper<User>().eq("account", account).eq("password", encryptPassword));
        if (user == null) {
            throw new MyException(ERROR_ACCOUNT_OR_PASSWORD_MISTAKE);
        }
//        5. 查询成功将信息存入redis中
        //创建token
        String token = IdUtil.simpleUUID();
        //信息脱敏
        redisHelper.setLoginKey(user, token);
//        6. 返回成功状态码
        return ResponseUtils.success(token);
    }

    @Override
    public Response<?> message(String phone) {
        if (!RegexValidationUtils.validatePhoneNumber(phone)) {
            throw new MyException(ERROR_INCORRECT_FORMAT);
        }
        String captcha = RandomUtil.randomNumbers(6);
        System.out.println(captcha);
        String key = USER_REGISTER_CODE_KEY + phone;
        stringRedisTemplate.opsForValue().set(key, captcha, USER_REGISTER_CODE_TTL, TimeUnit.MINUTES);
//TODO 发送验证码
//        AliyunSmsSenderUtil.sendMessage(phone, captcha);
        return ResponseUtils.success();
    }

    @Override
    public Response<?> updateTags(ArrayList<String> tags, Long userId, String token) {
        String jsonStr = JSONUtil.toJsonStr(tags);
        update(new UpdateWrapper<User>().eq("user_id", userId).set("tags", jsonStr));
        User newUser = getById(userId);
        redisHelper.setLoginKey(newUser, token);
        return ResponseUtils.success();
    }

    @Override
    public Response<?> logout(HttpServletRequest request) {
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            throw new MyException(ERROR_THE_USER_IS_NOT_LOGGED_IN);
        }
        String key = USER_LOGIN_KEY + token;
        stringRedisTemplate.delete(key);
        return ResponseUtils.success();
    }

    @Override
    public Response<?> userUpdate(UserVo updateUser, Long userId, String token) {
        User user = BeanUtil.copyProperties(updateUser, User.class);
        user.setUserId(userId);
        userMapper.updateById(user);
        User newUser = getById(user.getUserId());
        redisHelper.setLoginKey(newUser, token);
        return ResponseUtils.success();
    }

    @Override
    public Response<?> getSearchTags(int currentPage, ArrayList<String> tags) {
        Page<User> userPage = new Page<>(currentPage, PAGE_SIZE);
        QueryWrapper<User> userQueryWrapper = new QueryWrapper<>();
        for (String tag : tags) {
            userQueryWrapper.or().like("tags", tag);
        }
        Page<User> selectPage = userMapper.selectPage(userPage, userQueryWrapper);
        return ResponseUtils.success(selectPage);
    }

    @Override
    public Page<UserVo> matchUser(int currentPage, String username, Long userId) {
        //查询缓存
        String key = USER_RECOMMEND_KEY + userId + ":" + currentPage;
        String string = stringRedisTemplate.opsForValue().get(key);
        if (StrUtil.isNotBlank(string)) {
            return JSONUtil.toBean(string, Page.class);
        }


        //获取登录用户信息
        User loginUser = getById(userId);
        if (loginUser == null && !StrUtil.isBlank(username)) {
            throw new MyException(ERROR_THE_USER_IS_NOT_LOGGED_IN_NOT_SEARCH);
        }
        if (loginUser == null) {
            //未登录用户随机推荐用户
            return getRandomUser();
        }
        List<User> list;
        if (!StrUtil.isBlank(username)) {
            //用户模糊查询，先从数据库查询满足条件的用户
            list = list(new QueryWrapper<User>().like("username", username).ne("user_id", userId));
        } else {
            //查询数据库的id和标签
            list = list(new QueryWrapper<User>().select("user_id", "tags").isNotNull("tags").ne("user_id", loginUser.getUserId()));
        }
        //获取排序好的标签用户
        List<Pair<User, Integer>> sortUser = calculateUserSimilarity(list, loginUser);
        //处理分页问题
        ArrayList<Pair<User, Integer>> finalUserPairList = new ArrayList<>();
        int begin = (int) ((currentPage - 1) * PAGE_SIZE);
        int end = (int) (((currentPage - 1) * PAGE_SIZE) + PAGE_SIZE) - 1;
        // 手动整理最后一页
        if (sortUser.size() < end) {
            //剩余数量
            int temp = sortUser.size() - begin;
            if (temp <= 0) {
                return new Page<>();
            }
            for (int i = begin; i <= begin + temp - 1; i++) {
                finalUserPairList.add(sortUser.get(i));
            }
        } else {
            for (int i = begin; i < end; i++) {
                finalUserPairList.add(sortUser.get(i));
            }
        }
        // 获取排列后的UserId
        List<Long> userIdList = finalUserPairList.stream().map(pair -> pair.getKey().getUserId())
                .collect(Collectors.toList());
        List<UserVo> userVOList = getUserListByIdList(userIdList, loginUser.getUserId());
        Page<UserVo> userVoPage = new Page<>();
        userVoPage.setRecords(userVOList);
        userVoPage.setCurrent(currentPage);
        userVoPage.setSize(userVOList.size());
        userVoPage.setTotal(userVOList.size());
        return userVoPage;

    }

    @Override
    public List<String> getUserAvatarsByUserIds(List<Long> userIds) {
        if (userIds == null || userIds.isEmpty()) {
            return null;
        }
        List<User> users = userMapper.selectBatchIds(userIds);
        return users.stream().map(User::getAvatarUrl).collect(Collectors.toList());
    }

    @Override
    public Response<?> getDetailedUser(Long userId) {
        //1.获取用户信息
        User user = getUserById(userId);
        UserVo userVo = BeanUtil.copyProperties(user, UserVo.class);
        //2.设置当前登录用户是否关注此用户
        userVo.setIsFollow(followService.isFollowTheUser(userId));
        return ResponseUtils.success(userVo);
    }

    /**
     * 获取随机推荐用户
     *
     * @return
     */
    private Page<UserVo> getRandomUser() {
        //查询到用户id
        List<User> randomUser = userMapper.getRandomUser();
        List<UserVo> userVOList = randomUser.stream().map((item) -> {
            UserVo userVO = new UserVo();
            BeanUtils.copyProperties(item, userVO);
            return userVO;
        }).toList();
        Page<UserVo> userVoPage = new Page<>();
        userVoPage.setRecords(userVOList);
        userVoPage.setSize(userVOList.size());
        userVoPage.setTotal(userVOList.size());
        return userVoPage;
    }

    /**
     * 根据id列表获取用户
     *
     * @param userIdList
     * @param userId
     * @return
     */
    @Override
    public List<UserVo> getUserListByIdList(List<Long> userIdList, Long userId) {
        List<User> users = userMapper.selectBatchIds(userIdList);
        List<UserVo> userVos = new ArrayList<>();
        for (User user : users) {
            userVos.add(BeanUtil.copyProperties(user, UserVo.class));
        }
        return userVos;
    }

    @Override
    public Response<?> sendUpdateEmailCode(String email) {
        UserVo user = UserHolder.getUser();
        String code = RandomUtil.randomNumbers(6);
        String key = USER_UPDATE_EMAIL_KEY + user.getUserId();
        stringRedisTemplate.opsForValue().set(key, code, USER_UPDATE_EMAIL_TTL, TimeUnit.MINUTES);
        emailUtil.sendSimpleMail(email, UPDATE_EMAIL_TITLE, UPDATE_EMAIL_CONTENT + code);
        return ResponseUtils.success();
    }

    @Override
    public void updateUserWithCode(UserVo updateUser) {
        String key;
        boolean isPhone = false;
        if (StringUtils.isNotBlank(updateUser.getPhone())) {
            key = USER_UPDATE_PHONE_KEY + updateUser.getPhone();
            isPhone = true;
        } else {
            key = USER_UPDATE_EMAIL_KEY + updateUser.getEmail();
        }
        String correctCode = stringRedisTemplate.opsForValue().get(key);
        if (correctCode == null) {
            throw new MyException(PLEASE_SEND_THE_VERIFICATION_CODE_FIRST);
        }
        if (!correctCode.equals(updateUser.getCode())) {
            throw new MyException(ETHE_VERIFICATION_CODE_IS_INCORRECT);
        }
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        if (isPhone) {
            userLambdaQueryWrapper.eq(User::getPhone, updateUser.getPhone());
            User user = this.getOne(userLambdaQueryWrapper);
            if (user != null) {
                throw new MyException(THE_MOBILE_PHONE_NUMBER_HAS_BEEN_BOUND);
            }
        } else {
            userLambdaQueryWrapper.eq(User::getEmail, updateUser.getEmail());
            User user = this.getOne(userLambdaQueryWrapper);
            if (user != null) {
                throw new MyException(THE_EMAIL_ADDRESS_HAS_BEEN_BOUND);
            }
        }
        User user = new User();
        BeanUtils.copyProperties(updateUser, user);
        user.setUserId(Long.valueOf(UserHolder.getUser().getUserId()));
        this.updateById(user);
    }

    @Override
    public void forgetUserPasswordSendPhoneCode(String phone) {
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getPhone, phone);
        User user = getOne(userLambdaQueryWrapper);
        if (user == null) {
            throw new MyException(THE_MOBILE_PHONE_NUMBER_IS_NOT_BOUND_TO_AN_ACCOUNT);
        } else {
            String key = USER_FORGET_PASSWORD_KEY + phone;
            String code = RandomUtil.randomNumbers(4);
            AliyunSmsSenderUtil.sendMessage(phone, code);
            stringRedisTemplate.opsForValue().set(key, code, USER_FORGET_PASSWORD_TTL, TimeUnit.MINUTES);
        }

    }

    @Override
    public void checkCode(String phone, String code) {
        String key = USER_FORGET_PASSWORD_KEY + phone;
        String correctCode = stringRedisTemplate.opsForValue().get(key);
        if (correctCode == null) {
            throw new MyException(PLEASE_SEND_THE_VERIFICATION_CODE_FIRST);
        }
        if (!correctCode.equals(code)) {
            throw new MyException(ETHE_VERIFICATION_CODE_IS_INCORRECT);
        }
    }

    @Override
    public void updatePassword(String phone, String code, String password, String confirmPassword) {
        if (!password.equals(confirmPassword)) {
            throw new MyException(ERROR_PASSWORD_INCONSISTENT);
        }
        String key = USER_FORGET_PASSWORD_KEY + phone;
        String correctCode = stringRedisTemplate.opsForValue().get(key);
        if (correctCode == null) {
            throw new MyException(PLEASE_SEND_THE_VERIFICATION_CODE_FIRST);
        }
        if (!correctCode.equals(code)) {
            throw new MyException(ETHE_VERIFICATION_CODE_IS_INCORRECT);
        }
        LambdaQueryWrapper<User> userLambdaQueryWrapper = new LambdaQueryWrapper<>();
        userLambdaQueryWrapper.eq(User::getPhone, phone);
        User user = this.getOne(userLambdaQueryWrapper);
        String encryptPassword = DigestUtil.md5Hex(password);
        user.setPassword(encryptPassword);
        this.updateById(user);
        stringRedisTemplate.delete(key);
    }

    /**
         * 生成用户名
         *
         * @return 生成的用户名
         */
        private String generateUsername () {
            String suffix = RandomUtil.randomString(6);
            return USERNAME_PREFIX + suffix;
        }

        /**
         * 计算用户标签相似度，排序
         *
         * @param usersList 带标签信息的用户
         * @param loginUser 登录用户
         * @return 根据相似度高低排序的用户列表
         */
        private List<Pair<User, Integer>> calculateUserSimilarity (List < User > usersList, User loginUser){
            List<Pair<User, Integer>> res = new ArrayList<>();
            for (User user : usersList) {
                //将自己的标签排除
            /*if (user.getUserId().equals(loginUser.getUserId())) {
                continue;
            }*/
                //获取待比较用户的标签列表
                List<String> tagList = JSONUtil.toList(user.getTags(), String.class);
                //获取登录用户的标签列表
                List<String> loginUserTagList = JSONUtil.toList(loginUser.getTags(), String.class);
                //计算标签相似度
                int similarity = AlgorithmUtils.minDistance(loginUserTagList, tagList);
                res.add(new Pair<>(user, similarity));
            }
            // 按编辑距离由小到大排序
            return res.stream()
                    .sorted((a, b) -> (int) (a.getValue() - b.getValue()))
                    .collect(Collectors.toList());
        }

        public static boolean isAdmin () throws RuntimeException {
            UserVo user = UserHolder.getUser();
            if (user == null) {
                throw new RuntimeException();
            }
            return user.getUserRole() == ADMIN_CODE;
        }

        /**
         * 根据用户id获取用户信息
         * @param userId 用户id
         * @return 用户信息
         */
        public User getUserById (Long userId){
            return getById(userId);
        }


    }




